/**
 * @Author       : oak-c
 * @Date         : 2021-01-18 16:13:59
 * @Description  : redis
 * @LastEditors  : oak-c
 * @LastEditTime : 2021-01-19 20:30:58
 * @FilePath     : /src/modules/redis/index.ts
 */
import { ConfigService } from '@donews/nestjs-config'
import { Injectable, OnModuleInit } from '@nestjs/common'
import IORedis from 'ioredis'
import { CustomLoggerService } from '../logger'

@Injectable()
export class NRedis implements OnModuleInit {
    constructor(private readonly config: ConfigService, private readonly logger: CustomLoggerService) {}

    private readonly redisConfig: Record<string, any> = this.config.get('redis')
    private readonly prefix = `${this.config.get('service.name', 't')}:`.toLocaleUpperCase()
    private redis: IORedis.Cluster | IORedis.Redis

    public async onModuleInit() {
        const config = []
        if (Array.isArray(this.redisConfig.host) && this.redisConfig.host.length > 0) {
            this.redisConfig.host.map((r) => {
                const [h, p] = r.split(':')
                config.push({
                    host: h,
                    port: p,
                    password: this.redisConfig.password,
                    db: this.redisConfig.db,
                })
            })
        }
        if (this.redisConfig.cluster) {
            this.redis = new IORedis.Cluster(config)
        } else {
            this.redis = new IORedis(config[Math.floor(Math.random() * config.length)])
        }
        this.logger.log('Redis初始化成功!')
    }

    public async set(key: string, value: string | Record<string, any> | number, ttl = -1): Promise<boolean> {
        if (!key) {
            throw new Error('key for redis not be null')
        }
        if (ttl > 0) {
            await this.redis.set(this.prefix + key, JSON.stringify({ value }), 'EX', ttl)
            return true
        }
        await this.redis.set(this.prefix + key, JSON.stringify({ value }))
    }

    public async get(key: string): Promise<string | Record<string, any> | number> {
        const val: any = await this.redis.get(this.prefix + key)
        if (!val) {
            return null
        }
        const value = JSON.parse(val)
        return value.value || value
    }

    public async del(key: string) {
        await this.redis.del(this.prefix + key)
    }

    /**
     * 如果是集群，遍历节点
     * @param pattern
     */
    public async deleteKeys(pattern: string) {
        if (this.redisConfig.cluster) {
            const nodes: IORedis.Redis[] = (this.redis as IORedis.Cluster).nodes()
            for await (const node of nodes) {
                await this.delKeysFromNode(node, pattern)
            }
        }
        await this.delKeysFromNode(this.redis as IORedis.Redis, pattern)
    }

    /**
     * 通过stream流pipe删除keys
     * @param node
     * @param pattern
     */
    private async delKeysFromNode(node: IORedis.Redis, pattern: string) {
        if (typeof node.scanStream !== 'function') return
        return new Promise((resolve) => {
            const stream = node.scanStream({ match: this.prefix + pattern })
            stream.on('data', (keys) => {
                if (keys.length > 0) {
                    const pipeline = node.pipeline()
                    keys.forEach((k) => {
                        pipeline.del(k)
                        this.logger.log(`[清理缓存] - ${k}`)
                    })
                    pipeline.exec()
                }
            })
            stream.on('end', () => {
                const options: any = (node as any)?.options
                this.logger.log(`[清理缓存] - 结束 - ${options?.host}:${options?.port}:${options?.role} - ${pattern}`)
                resolve(true)
            })
            stream.on('error', (err) => {
                this.logger.error(`[清理缓存] - 错误 - ${err.message}`)
                resolve(false)
            })
        })
    }
}
